#ifndef D3DGAME_H_
#include "../Core/D3DGame.h"
#endif

#include <sstream>

LRESULT CALLBACK
MainWndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lPARAM )
{
	static D3DGame* game = 0;

	switch( msg )
	{
	case WM_CREATE:
		{
			/* Get the this pointer we passed to createWindow via the lParam parameter. */
			CREATESTRUCT* cs = (CREATESTRUCT*)lPARAM;
			game = (D3DGame*)cs->lpCreateParams;
			return 0;
		}
	}

	/* Don't start processing messages until after WM_CREATE. */
	if( game )
	{
		return game->msgProc(msg, wParam, lPARAM);
	}
	else
	{
		return DefWindowProc(hWnd, msg, wParam, lPARAM);
	}
}

/* Constructor. */
D3DGame::D3DGame( HINSTANCE hInstance )
	: m_hAppInstance( hInstance )
	, m_hMainWnd( NULL )
	, m_bAppPaused( false )
	, m_bMinimize( false )
	, m_bMaximize( false )
	, m_bResizing( false )
	, m_FrameStat( L"" )
	, m_pD3DDevice( NULL )
	, m_pSwapChain( NULL )
	, m_pDepthStencilBuffer( NULL )
	, m_pRenderTargetView( NULL )
	, m_pDepthStencilView( NULL )
	, m_pFont( NULL )
	, m_sMainWndCaption( L"D3D_Application" )
	, m_ClearColor( D3DXCOLOR( 0.0f, 0.0f, 1.0f, 1.0f ) )
	, m_iWindowHeight( 600 )
	, m_iWindowWidth( 800 )
{
	m_D3D10DriverType = D3D10_DRIVER_TYPE_HARDWARE;
}

/* Destructor. */
D3DGame::~D3DGame()
{
	ReleaseCOM( m_pRenderTargetView );
	ReleaseCOM( m_pDepthStencilView );
	ReleaseCOM( m_pSwapChain );
	ReleaseCOM( m_pDepthStencilBuffer );
	ReleaseCOM( m_pD3DDevice );
	ReleaseCOM( m_pFont );
}

HINSTANCE D3DGame::GetAppInstance( void )
{
	return m_hAppInstance;
}

HWND D3DGame::GetMainWindow( void )
{
	return m_hMainWnd;
}

int D3DGame::run()
{
	MSG msg = {0};

	m_Timer.Reset();

	while( WM_QUIT != msg.message)
	{
		/* If there is a window message, then process the message. */
		if( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) )
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}

		else
		{
			/* Do other stuff. */
			m_Timer.Tick();

			if( !m_bAppPaused )
			{
				onUpdateScene( m_Timer.GetDeltaTime() );
			}
			else
			{
				Sleep(50);
			}

			drawScene();
		}
	}

	return (int)msg.wParam;
}


void D3DGame::initApp( void )
{
	initMainWindow();
	initDirect3D();

	D3DX10_FONT_DESC fontDesc;
	ZeroMemory(&fontDesc, sizeof(D3DX10_FONT_DESC) );

	fontDesc.Height				= 24;
	fontDesc.Weight				= 0;
	fontDesc.Width				= 0;
	fontDesc.MipLevels			= 1;
	fontDesc.Italic				= true;
	fontDesc.CharSet			= DEFAULT_CHARSET;
	fontDesc.OutputPrecision	= OUT_DEFAULT_PRECIS;
	fontDesc.Quality			= DEFAULT_QUALITY;
	fontDesc.PitchAndFamily		= DEFAULT_PITCH | FF_DONTCARE;
	wcscpy(fontDesc.FaceName, L"Times New Roman");

	D3DX10CreateFontIndirect( m_pD3DDevice, &fontDesc, &m_pFont );
}

void D3DGame::onResize( void )
{
	/* Release all the old views, as they hold the information of the buffers we will be destroying. Also depth/stencil buffer. */
	ReleaseCOM(m_pRenderTargetView);
	ReleaseCOM(m_pDepthStencilBuffer);
	ReleaseCOM(m_pDepthStencilView);

	/* Resize the swap chain and recreate the render target view. */
	HR( m_pSwapChain->ResizeBuffers( 1, m_iWindowWidth, m_iWindowHeight, DXGI_FORMAT_R8G8B8A8_UNORM, 0 ) );
	ID3D10Texture2D* pBackBuffer;
	HR( m_pSwapChain->GetBuffer( 0, __uuidof(ID3D10Texture2D), reinterpret_cast<void**>(&pBackBuffer) ) );
	HR( m_pD3DDevice->CreateRenderTargetView(pBackBuffer, 0, &m_pRenderTargetView ) );
	ReleaseCOM(pBackBuffer);

	/* Create the depth stencil buffer and view. */
	D3D10_TEXTURE2D_DESC depthStencilDesc;
	ZeroMemory(&depthStencilDesc, sizeof(D3D10_TEXTURE2D_DESC) );

	depthStencilDesc.Width				= m_iWindowWidth;
	depthStencilDesc.Height				= m_iWindowHeight;
	depthStencilDesc.MipLevels			= 1;
	depthStencilDesc.ArraySize			= 1;
	depthStencilDesc.Format				= DXGI_FORMAT_D24_UNORM_S8_UINT;
	depthStencilDesc.SampleDesc.Count	= 1;
	depthStencilDesc.SampleDesc.Quality	= 0;
	depthStencilDesc.Usage				= D3D10_USAGE_DEFAULT;
	depthStencilDesc.BindFlags			= D3D10_BIND_DEPTH_STENCIL;
	depthStencilDesc.CPUAccessFlags		= 0;
	depthStencilDesc.MiscFlags			= 0;

	HR( m_pD3DDevice->CreateTexture2D( &depthStencilDesc, 0, &m_pDepthStencilBuffer ) );
	HR( m_pD3DDevice->CreateDepthStencilView( m_pDepthStencilBuffer, 0, &m_pDepthStencilView) );

	/* Bind the render target view and depth stencil view to the pipeline. */
	m_pD3DDevice->OMSetRenderTargets( 1, &m_pRenderTargetView, m_pDepthStencilView );

	/* Set the view transform. */
	D3D10_VIEWPORT vp;
	vp.TopLeftX = 0;
	vp.TopLeftY = 0;
	vp.Width	= m_iWindowWidth;
	vp.Height	= m_iWindowHeight;
	vp.MinDepth	= 0.0f;
	vp.MaxDepth	= 1.0f;

	m_pD3DDevice->RSSetViewports( 1, &vp );
}

void D3DGame::onUpdateScene( float fTimeStep )
{
	/* Here we will compute the average frames per second, and also the average time it takes to render a frame. */
	static int framCnt	= 0;
	static float t_base = 0.0f;

	framCnt++;

	float var = m_Timer.GetGameTime() - t_base;

	/* Compute the averages over one second period. */
	if( ( m_Timer.GetGameTime() - t_base ) >= 1.0f )
	{
		float fps = (float)framCnt;
		float mspf = 1000.0f / fps;

		std::wostringstream outs;
		outs.precision(6);
		outs << L"FPS" << fps << L"\n"
			 << L"Milliseconds : Per Frame : " << mspf;

		m_FrameStat = outs.str();

		/* Reset the next average. */
		framCnt = 0;
		t_base	+= 1.0f;
	}
}

void D3DGame::drawScene( void )
{
	m_pD3DDevice->ClearRenderTargetView( m_pRenderTargetView, m_ClearColor ); 
	m_pD3DDevice->ClearDepthStencilView( m_pDepthStencilView, D3D10_CLEAR_DEPTH | D3D10_CLEAR_STENCIL, 1.0f, 0 );
}

LRESULT D3DGame::msgProc( UINT msg, WPARAM wParam, LPARAM lParam )
{
	switch( msg )
	{
		/* 
			WM_ACTIVE is sent when the window is activated or deactivated.
			We pause the game when the window is deactivated and unpause when the window is activated back.
		*/
	case WM_ACTIVATE:
		{
			if( LOWORD(wParam) == WA_INACTIVE )
			{
				m_bAppPaused = true;
				m_Timer.Stop();
			}
			else
			{
				m_bAppPaused = true;
				m_Timer.Start();
			}
			return 0;
		}

	case WM_SIZE:
		{
			/* Save the client window size. */
			m_iWindowHeight	= LOWORD(lParam);
			m_iWindowWidth	= HIWORD(lParam);

			if( m_pD3DDevice )
			{
				if( SIZE_MINIMIZED == wParam )
				{
					m_bAppPaused	= true;
					m_bMaximize		= false;
					m_bMinimize		= true;
				}
				else if( SIZE_MAXIMIZED == wParam )
				{
					m_bAppPaused	= false;
					m_bMinimize		= false;
					m_bMaximize		= true;
					onResize();
				}
				else if(  SIZE_RESTORED == wParam )
				{
					m_bAppPaused	= false;
					m_bMinimize		= false;
					onResize();
				}
				else if( m_bResizing )
				{
					// If user is dragging the resize bars, we do not resize 
					// the buffers here because as the user continuously 
					// drags the resize bars, a stream of WM_SIZE messages are
					// sent to the window, and it would be pointless (and slow)
					// to resize for each WM_SIZE message received from dragging
					// the resize bars.  So instead, we reset after the user is 
					// done resizing the window and releases the resize bars, which 
					// sends a WM_EXITSIZEMOVE message.
				}
				else
				{
					onResize();
				}
			}
			return 0;
		}
	case WM_ENTERSIZEMOVE:
		{
			m_bAppPaused	= true;
			m_bResizing		= true;
			m_Timer.Stop();
			return 0;
		}
	case WM_EXITSIZEMOVE:
		{
			m_bAppPaused	= false;
			m_bResizing		= false;
			m_Timer.Start();
			onResize();
			return 0;
		}
	case WM_DESTROY:
		{
			PostQuitMessage(0);
			return 0;
		}
	case WM_MENUCHAR:
		{
			return MAKELRESULT( 0,MNC_CLOSE );
		}
	case WM_GETMINMAXINFO:
		{
			((MINMAXINFO*)lParam)->ptMinTrackSize.x = 200;
			((MINMAXINFO*)lParam)->ptMinTrackSize.y = 200;
			return 0;
		}
	}

	return DefWindowProc(m_hMainWnd, msg, wParam, lParam);
}

void D3DGame::initMainWindow( void )
{
	WNDCLASS wc;
	ZeroMemory(&wc, sizeof(WNDCLASS) );

	wc.style			= CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc		= MainWndProc;
	wc.cbClsExtra		= 0;
	wc.cbWndExtra		= 0;
	wc.hInstance		= m_hAppInstance;
	wc.hIcon			= LoadIcon(0, IDI_APPLICATION);
	wc.hCursor			= LoadCursor( 0, IDC_ARROW );
	wc.hbrBackground	= (HBRUSH)GetStockObject(NULL_BRUSH);
	wc.lpszMenuName		= 0;
	wc.lpszClassName	= L"D3DWndClassName";

	if( !RegisterClass(&wc) )
	{
		MessageBox( 0, L"Failed to Register class", 0, 0 );
		PostQuitMessage(0);
	}

	/* Compute window rectangle dimensions based on requested client area dimension. */
	RECT R = { 0, 0, m_iWindowWidth, m_iWindowHeight };
	AdjustWindowRect( &R, WS_OVERLAPPEDWINDOW, false );
	int width = R.right - R.left;
	int height = R.bottom - R.top;

	m_hMainWnd = CreateWindow( L"D3DWndClassName", m_sMainWndCaption.c_str(), 
								WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, width, height, 0, 0, m_hAppInstance, this );
	if( !m_hMainWnd )
	{
		MessageBox(0, L"Window Creation failed", 0, 0 );
		PostQuitMessage(0);
	}

	ShowWindow( m_hMainWnd, SW_SHOW );
	UpdateWindow(m_hMainWnd);
}

void D3DGame::initDirect3D( void )
{
	DXGI_SWAP_CHAIN_DESC sd;
	//ZeroMemory(&sd, sizeof(DXGI_SWAP_CHAIN_DESC) );

	sd.BufferDesc.Width		= m_iWindowWidth;
	sd.BufferDesc.Height	= m_iWindowHeight;
	sd.BufferDesc.RefreshRate.Numerator	= 60;
	sd.BufferDesc.RefreshRate.Denominator = 1;
	sd.BufferDesc.Format	= DXGI_FORMAT_R8G8_UNORM;
	sd.BufferDesc.ScanlineOrdering	= DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
	sd.BufferDesc.Scaling	= DXGI_MODE_SCALING_UNSPECIFIED;

	sd.SampleDesc.Count		= 1;
	sd.SampleDesc.Quality	= 0;

	sd.BufferUsage			= DXGI_USAGE_RENDER_TARGET_OUTPUT;
	sd.BufferCount			= 1;
	sd.OutputWindow			= m_hMainWnd;
	sd.Windowed				= true;
	sd.SwapEffect			= DXGI_SWAP_EFFECT_DISCARD;
	sd.Flags				= 0;

	/* Create the device. */
	HR( D3D10CreateDeviceAndSwapChain(0, m_D3D10DriverType, 0, 0, D3D10_SDK_VERSION, &sd, &m_pSwapChain, &m_pD3DDevice) );
	
	onResize();
}




